#include "writer.h"
#include <utility>
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <iostream>
#include <sstream>
#include <iomanip>

#if _MSC_VER >= 1400 // VC++ 8.0
#pragma warning( disable : 4996 )   // disable warning about strdup being deprecated.
#endif

namespace Json {

    static bool
    isControlCharacter(char ch)
    {
        return ch > 0 && ch <= 0x1F;
    }

    static bool
    containsControlCharacter( const char* str )
    {
        while ( *str )
        {
            if ( isControlCharacter( *(str++) ) )
                return true;
        }
        return false;
    }

    static void
    uintToString( unsigned int value,
            char *&current )
    {
        *--current = 0;
        do
        {
            *--current = (value % 10) + '0';
            value /= 10;
        }
        while ( value != 0 );
    }

    static void
    lintToString(long long value, char*& current)
    {
        *--current = 0;
        do
        {
            *--current = (value % 10) + '0';
            value /= 10;
        }
        while ( value != 0 );
    }

    std::string
    valueToString( Value::Int value )
    {
        char buffer[32];
        char *current = buffer + sizeof (buffer);
        bool isNegative = value < 0;
        if ( isNegative )
            value = -value;
        uintToString( Value::UInt(value), current );
        if ( isNegative )
            *--current = '-';
        assert( current >= buffer );
        return current;
    }

    std::string
    valueToString(Value::LongInt value)
    {
        char buffer[32];
        char *current = buffer + sizeof (buffer);
        bool isNegative = value < 0;
        if ( isNegative )
            value = -value;
        lintToString( Value::LongInt(value), current );
        if ( isNegative )
            *--current = '-';
        assert( current >= buffer );
        return current;
    }

    std::string
    valueToString( Value::UInt value )
    {
        char buffer[32];
        char *current = buffer + sizeof (buffer);
        uintToString( value, current );
        assert( current >= buffer );
        return current;
    }

    std::string
    valueToString( double value )
    {
        char buffer[32];
#if defined(_MSC_VER) && defined(__STDC_SECURE_LIB__) // Use secure version with visual studio 2005 to avoid warning. 
        sprintf_s(buffer, sizeof (buffer), "%#.16g", value);
#else 
        sprintf(buffer, "%#.16g", value);
#endif
        char* ch = buffer + strlen(buffer) - 1;
        if (*ch != '0') return buffer; // nothing to truncate, so save time
        while (ch > buffer && *ch == '0')
        {
            --ch;
        }
        char* last_nonzero = ch;
        while (ch >= buffer)
        {
            switch (*ch)
            {
                case '0':
                case '1':
                case '2':
                case '3':
                case '4':
                case '5':
                case '6':
                case '7':
                case '8':
                case '9':
                    --ch;
                    continue;
                case '.':
                    // Truncate zeroes to save bytes in output, but keep one.
                    *(last_nonzero + 2) = '\0';
                    return buffer;
                default:
                    return buffer;
            }
        }
        return buffer;
    }

    std::string
    valueToString( bool value )
    {
        return value ? "true" : "false";
    }

    std::string
    valueToQuotedString( const char *value )
    {
        // Not sure how to handle unicode...
        if (strpbrk(value, "\"\\\b\f\n\r\t") == NULL && !containsControlCharacter( value ))
            return std::string("\"") + value + "\"";
        // We have to walk value and escape any special characters.
        // Appending to std::string is not efficient, but this should be rare.
        // (Note: forward slashes are *not* rare, but I am not escaping them.)
        unsigned maxsize = strlen(value)*2 + 3; // allescaped+quotes+NULL
        std::string result;
        result.reserve(maxsize); // to avoid lots of mallocs
        result += "\"";
        for (const char* c = value; *c != 0; ++c)
        {
            switch (*c)
            {
                case '\"':
                    result += "\\\"";
                    break;
                case '\\':
                    result += "\\\\";
                    break;
                case '\b':
                    result += "\\b";
                    break;
                case '\f':
                    result += "\\f";
                    break;
                case '\n':
                    result += "\\n";
                    break;
                case '\r':
                    result += "\\r";
                    break;
                case '\t':
                    result += "\\t";
                    break;
                    //case '/':
                    // Even though \/ is considered a legal escape in JSON, a bare
                    // slash is also legal, so I see no reason to escape it.
                    // (I hope I am not misunderstanding something.
                    // blep notes: actually escaping \/ may be useful in javascript to avoid </ 
                    // sequence.
                    // Should add a flag to allow this compatibility mode and prevent this 
                    // sequence from occurring.
                default:
                    if ( isControlCharacter( *c ) )
                    {
                        std::ostringstream oss;
                        oss << "\\u" << std::hex << std::uppercase << std::setfill('0') << std::setw(4) << static_cast<int> (*c);
                        result += oss.str();
                    }
                    else
                    {
                        result += *c;
                    }
                    break;
            }
        }
        result += "\"";
        return result;
    }

    // Class Writer
    // //////////////////////////////////////////////////////////////////

    Writer::~Writer()
    {
    }


    // Class FastWriter
    // //////////////////////////////////////////////////////////////////

    FastWriter::FastWriter()
    : yamlCompatiblityEnabled_( false )
    {
    }

    void
    FastWriter::enableYAMLCompatibility()
    {
        yamlCompatiblityEnabled_ = true;
    }

    std::string
    FastWriter::write( const Value &root )
    {
        document_ = "";
        writeValue( root );
        document_ += "\n";
        return document_;
    }

    void
    FastWriter::writeValue( const Value &value )
    {
        switch ( value.type() )
        {
            case nullValue:
                document_ += "";
                break;
            case intValue:
                document_ += valueToString( value.asInt() );
                break;
            case longValue:
                document_ += valueToString( value.asLong() );
                break;
            case uintValue:
                document_ += valueToString( value.asUInt() );
                break;
            case realValue:
                document_ += valueToString( value.asDouble() );
                break;
            case stringValue:
                document_ += valueToQuotedString( value.asCString() );
                break;
            case booleanValue:
                document_ += valueToString( value.asBool() );
                break;
            case arrayValue:
            {
                document_ += "[";
                int size = value.size();
                for ( int index = 0; index < size; ++index )
                {
                    if ( index > 0 )
                        document_ += ",";
                    writeValue( value[index] );
                }
                document_ += "]";
            }
                break;
            case objectValue:
            {
                Value::Members members( value.getMemberNames() );
                document_ += "{";
                for ( Value::Members::iterator it = members.begin();
                        it != members.end();
                        ++it )
                {
                    const std::string &name = *it;
                    if ( it != members.begin() )
                        document_ += ",";
                    document_ += valueToQuotedString( name.c_str() );
                    document_ += yamlCompatiblityEnabled_ ? ": "
                            : ":";
                    writeValue( value[name] );
                }
                document_ += "}";
            }
                break;
        }
    }


    // Class StyledWriter
    // //////////////////////////////////////////////////////////////////

    StyledWriter::StyledWriter()
    : rightMargin_( 74 )
    , indentSize_( 3 )
    {
        singleLine_ = false;
    }

    std::string
    StyledWriter::write( const Value &root )
    {
        document_ = "";
        addChildValues_ = false;
        indentString_ = "";
        writeCommentBeforeValue( root );
        writeValue( root );
        writeCommentAfterValueOnSameLine( root );
        document_ += "\n";
        return document_;
    }

    void
    StyledWriter::writeValue( const Value &value )
    {
        switch ( value.type() )
        {
            case nullValue:
                pushValue( "{}" );
                break;
            case intValue:
                pushValue( valueToString( value.asInt() ) );
                break;
            case longValue:
                pushValue( valueToString( value.asLong() ) );
                break;
            case uintValue:
                pushValue( valueToString( value.asUInt() ) );
                break;
            case realValue:
                pushValue( valueToString( value.asDouble() ) );
                break;
            case stringValue:
                pushValue( valueToQuotedString( value.asCString() ) );
                break;
            case booleanValue:
                pushValue( valueToString( value.asBool() ) );
                break;
            case arrayValue:
                writeArrayValue( value);
                break;
            case objectValue:
            {
                Value::Members members( value.getMemberNames() );
                if ( members.empty() )
                    pushValue( "{}" );
                else
                {
                    writeWithIndent( "{" );
                    indent();
                    Value::Members::iterator it = members.begin();
                    while ( true )
                    {
                        const std::string &name = *it;
                        const Value &childValue = value[name];
                        writeCommentBeforeValue( childValue );
                        writeWithIndent( valueToQuotedString( name.c_str() ) );
                        document_ += (singleLine_) ? (":") : (" : ");
                        writeValue( childValue );
                        if ( ++it == members.end() )
                        {
                            writeCommentAfterValueOnSameLine( childValue );
                            break;
                        }
                        document_ += ",";
                        writeCommentAfterValueOnSameLine( childValue );
                    }
                    unindent();
                    writeWithIndent( "}" );
                }
            }
                break;
        }
    }

    void
    StyledWriter::writeArrayValue( const Value &value )
    {
        unsigned size = value.size();
        if ( size == 0 )
            pushValue( "[]" );
        else
        {
            bool isArrayMultiLine = isMultineArray( value );
            if ( isArrayMultiLine )
            {
                writeWithIndent( "[" );
                indent();
                bool hasChildValue = !childValues_.empty();
                unsigned index = 0;
                while ( true )
                {
                    const Value &childValue = value[index];
                    writeCommentBeforeValue( childValue );
                    if ( hasChildValue )
                        writeWithIndent( childValues_[index] );
                    else
                    {
                        writeIndent();
                        writeValue( childValue );
                    }
                    if ( ++index == size )
                    {
                        writeCommentAfterValueOnSameLine( childValue );
                        break;
                    }
                    document_ += ",";
                    writeCommentAfterValueOnSameLine( childValue );
                }
                unindent();
                writeWithIndent( "]" );
            }
            else // output on a single line
            {
                assert( childValues_.size() == size );
                document_ += "[ ";
                for ( unsigned index = 0; index < size; ++index )
                {
                    if ( index > 0 )
                        document_ += ", ";
                    document_ += childValues_[index];
                }
                document_ += " ]";
            }
        }
    }

    bool
    StyledWriter::isMultineArray( const Value &value )
    {
        int size = value.size();
        bool isMultiLine = size * 3 >= rightMargin_ ;
        childValues_.clear();
        for ( int index = 0; index < size  &&  !isMultiLine; ++index )
        {
            const Value &childValue = value[index];
            isMultiLine = isMultiLine  ||
                    ( (childValue.isArray()  ||  childValue.isObject())  &&
                    childValue.size() > 0 );
        }
        if ( !isMultiLine ) // check if line length > max line length
        {
            childValues_.reserve( size );
            addChildValues_ = true;
            int lineLength = 4 + (size - 1)*2; // '[ ' + ', '*n + ' ]'
            for ( int index = 0; index < size  &&  !isMultiLine; ++index )
            {
                writeValue( value[index] );
                lineLength += int( childValues_[index].length() );
                isMultiLine = isMultiLine  &&  hasCommentForValue( value[index] );
            }
            addChildValues_ = false;
            isMultiLine = isMultiLine  ||  lineLength >= rightMargin_;
        }
        return isMultiLine;
    }

    void
    StyledWriter::pushValue( const std::string &value )
    {
        if ( addChildValues_ )
            childValues_.push_back( value );
        else
            document_ += value;
    }

    void
    StyledWriter::writeIndent()
    {
        if ( !document_.empty() )
        {
            char last = document_[document_.length() - 1];
            if ( last == ' ' )     // already indented
                return;
            if ( last != '\n' && !singleLine_)    // Comments may add new-line
                document_ += '\n';
        }
        if (!singleLine_)
        {
            document_ += indentString_;
        }
    }

    void
    StyledWriter::writeWithIndent( const std::string &value )
    {
        if (!singleLine_)
        {
            writeIndent();
        }
        document_ += value;
    }

    void
    StyledWriter::indent()
    {
        indentString_ += std::string( indentSize_, ' ' );
    }

    void
    StyledWriter::unindent()
    {
        assert( int(indentString_.size()) >= indentSize_ );
        indentString_.resize( indentString_.size() - indentSize_ );
    }

    void
    StyledWriter::writeCommentBeforeValue( const Value &root )
    {
        return;
        if ( !root.hasComment( commentBefore ) )
            return;
        document_ += normalizeEOL( root.getComment( commentBefore ) );
        document_ += "\n";
    }

    void
    StyledWriter::writeCommentAfterValueOnSameLine( const Value &root )
    {
        return;
        if ( root.hasComment( commentAfterOnSameLine ) )
            document_ += " " + normalizeEOL( root.getComment( commentAfterOnSameLine ) );

        if ( root.hasComment( commentAfter ) )
        {
            document_ += "\n";
            document_ += normalizeEOL( root.getComment( commentAfter ) );
            document_ += "\n";
        }
    }

    bool
    StyledWriter::hasCommentForValue( const Value &value )
    {
        return value.hasComment( commentBefore )
                ||  value.hasComment( commentAfterOnSameLine )
                ||  value.hasComment( commentAfter );
    }

    std::string
    StyledWriter::normalizeEOL( const std::string &text )
    {
        std::string normalized;
        normalized.reserve( text.length() );
        const char *begin = text.c_str();
        const char *end = begin + text.length();
        const char *current = begin;
        while ( current != end )
        {
            char c = *current++;
            if ( c == '\r' ) // mac or dos EOL
            {
                if ( *current == '\n' ) // convert dos EOL
                    ++current;
                normalized += '\n';
            }
            else // handle unix EOL & other char
                normalized += c;
        }
        return normalized;
    }


    // Class StyledStreamWriter
    // //////////////////////////////////////////////////////////////////

    StyledStreamWriter::StyledStreamWriter( std::string indentation )
    : document_(NULL)
    , rightMargin_( 74 )
    , indentation_( indentation )
    {
    }

    void
    StyledStreamWriter::write( std::ostream &out, const Value &root )
    {
        document_ = &out;
        addChildValues_ = false;
        indentString_ = "";
        writeCommentBeforeValue( root );
        writeValue( root );
        writeCommentAfterValueOnSameLine( root );
        *document_ << "\n";
        document_ = NULL; // Forget the stream, for safety.
    }

    void
    StyledStreamWriter::writeValue( const Value &value )
    {
        switch ( value.type() )
        {
            case nullValue:
                pushValue( "null" );
                break;
            case intValue:
                pushValue( valueToString( value.asInt() ) );
                break;
            case longValue:
                pushValue( valueToString( value.asLong() ) );
                break;
            case uintValue:
                pushValue( valueToString( value.asUInt() ) );
                break;
            case realValue:
                pushValue( valueToString( value.asDouble() ) );
                break;
            case stringValue:
                pushValue( valueToQuotedString( value.asCString() ) );
                break;
            case booleanValue:
                pushValue( valueToString( value.asBool() ) );
                break;
            case arrayValue:
                writeArrayValue( value);
                break;
            case objectValue:
            {
                Value::Members members( value.getMemberNames() );
                if ( members.empty() )
                    pushValue( "{}" );
                else
                {
                    writeWithIndent( "{" );
                    indent();
                    Value::Members::iterator it = members.begin();
                    while ( true )
                    {
                        const std::string &name = *it;
                        const Value &childValue = value[name];
                        writeCommentBeforeValue( childValue );
                        writeWithIndent( valueToQuotedString( name.c_str() ) );
                        *document_ << " : ";
                        writeValue( childValue );
                        if ( ++it == members.end() )
                        {
                            writeCommentAfterValueOnSameLine( childValue );
                            break;
                        }
                        *document_ << ",";
                        writeCommentAfterValueOnSameLine( childValue );
                    }
                    unindent();
                    writeWithIndent( "}" );
                }
            }
                break;
        }
    }

    void
    StyledStreamWriter::writeArrayValue( const Value &value )
    {
        unsigned size = value.size();
        if ( size == 0 )
            pushValue( "[]" );
        else
        {
            bool isArrayMultiLine = isMultineArray( value );
            if ( isArrayMultiLine )
            {
                writeWithIndent( "[" );
                indent();
                bool hasChildValue = !childValues_.empty();
                unsigned index = 0;
                while ( true )
                {
                    const Value &childValue = value[index];
                    writeCommentBeforeValue( childValue );
                    if ( hasChildValue )
                        writeWithIndent( childValues_[index] );
                    else
                    {
                        writeIndent();
                        writeValue( childValue );
                    }
                    if ( ++index == size )
                    {
                        writeCommentAfterValueOnSameLine( childValue );
                        break;
                    }
                    *document_ << ",";
                    writeCommentAfterValueOnSameLine( childValue );
                }
                unindent();
                writeWithIndent( "]" );
            }
            else // output on a single line
            {
                assert( childValues_.size() == size );
                *document_ << "[ ";
                for ( unsigned index = 0; index < size; ++index )
                {
                    if ( index > 0 )
                        *document_ << ", ";
                    *document_ << childValues_[index];
                }
                *document_ << " ]";
            }
        }
    }

    bool
    StyledStreamWriter::isMultineArray( const Value &value )
    {
        int size = value.size();
        bool isMultiLine = size * 3 >= rightMargin_ ;
        childValues_.clear();
        for ( int index = 0; index < size  &&  !isMultiLine; ++index )
        {
            const Value &childValue = value[index];
            isMultiLine = isMultiLine  ||
                    ( (childValue.isArray()  ||  childValue.isObject())  &&
                    childValue.size() > 0 );
        }
        if ( !isMultiLine ) // check if line length > max line length
        {
            childValues_.reserve( size );
            addChildValues_ = true;
            int lineLength = 4 + (size - 1)*2; // '[ ' + ', '*n + ' ]'
            for ( int index = 0; index < size  &&  !isMultiLine; ++index )
            {
                writeValue( value[index] );
                lineLength += int( childValues_[index].length() );
                isMultiLine = isMultiLine  &&  hasCommentForValue( value[index] );
            }
            addChildValues_ = false;
            isMultiLine = isMultiLine  ||  lineLength >= rightMargin_;
        }
        return isMultiLine;
    }

    void
    StyledStreamWriter::pushValue( const std::string &value )
    {
        if ( addChildValues_ )
            childValues_.push_back( value );
        else
            *document_ << value;
    }

    void
    StyledStreamWriter::writeIndent()
    {
        /*
          Some comments in this method would have been nice. ;-)

         if ( !document_.empty() )
         {
            char last = document_[document_.length()-1];
            if ( last == ' ' )     // already indented
               return;
            if ( last != '\n' )    // Comments may add new-line
         *document_ << '\n';
         }
         */
        *document_ << '\n' << indentString_;
    }

    void
    StyledStreamWriter::writeWithIndent( const std::string &value )
    {
        writeIndent();
        *document_ << value;
    }

    void
    StyledStreamWriter::indent()
    {
        indentString_ += indentation_;
    }

    void
    StyledStreamWriter::unindent()
    {
        assert( indentString_.size() >= indentation_.size() );
        indentString_.resize( indentString_.size() - indentation_.size() );
    }

    void
    StyledStreamWriter::writeCommentBeforeValue( const Value &root )
    {
        if ( !root.hasComment( commentBefore ) )
            return;
        *document_ << normalizeEOL( root.getComment( commentBefore ) );
        *document_ << "\n";
    }

    void
    StyledStreamWriter::writeCommentAfterValueOnSameLine( const Value &root )
    {
        if ( root.hasComment( commentAfterOnSameLine ) )
            *document_ << " " + normalizeEOL( root.getComment( commentAfterOnSameLine ) );

        if ( root.hasComment( commentAfter ) )
        {
            *document_ << "\n";
            *document_ << normalizeEOL( root.getComment( commentAfter ) );
            *document_ << "\n";
        }
    }

    bool
    StyledStreamWriter::hasCommentForValue( const Value &value )
    {
        return value.hasComment( commentBefore )
                ||  value.hasComment( commentAfterOnSameLine )
                ||  value.hasComment( commentAfter );
    }

    std::string
    StyledStreamWriter::normalizeEOL( const std::string &text )
    {
        std::string normalized;
        normalized.reserve( text.length() );
        const char *begin = text.c_str();
        const char *end = begin + text.length();
        const char *current = begin;
        while ( current != end )
        {
            char c = *current++;
            if ( c == '\r' ) // mac or dos EOL
            {
                if ( *current == '\n' ) // convert dos EOL
                    ++current;
                normalized += '\n';
            }
            else // handle unix EOL & other char
                normalized += c;
        }
        return normalized;
    }

    std::ostream& operator<<( std::ostream &sout, const Value &root )
    {
        Json::StyledStreamWriter writer;
        writer.write(sout, root);
        return sout;
    }


} // namespace Json
